#define PrynInternal
#define PrynImplementation
#include <pryn.h>
#include <stdarg.h>

PrynExport (PrynResult, prynCopyMemory, (void *target, const void *source, uintptr_t length))
{
	uintptr_t offset;

	if (!target || !source) return PrynResultNullArgument;
	for (offset = 0; offset < length; offset ++)
		((char *) target) [offset] = ((char *) source) [offset];
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynMemoryEqual, (const void *a, const void *b, uintptr_t length))
{
	uintptr_t offset;

	if (a == b) return PrynResultTrue;
	if (!a || !b) return PrynResultFalse;
	for (offset = 0; offset < length; offset ++)
		if (((char *) a) [offset] != ((char *) b) [offset])
			return PrynResultFalse;
	return PrynResultTrue;
}

PrynExport (PrynResult, prynSetMemory, (void *data, char value, uintptr_t length))
{
	uintptr_t offset;

	if (!data) return PrynResultNullArgument;
	for (offset = 0; offset < length; offset ++)
		((char *) data) [offset] = value;
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynTimeCreate, (PrynTime *time, int year, int month, int day, int hour, int minute, int second))
{
	if (!time) return PrynResultNullArgument;
	
	time->year = year;
	time->remainder = ((month & PrynTimeMonthMask) << PrynTimeMonthShift)
		| ((day & PrynTimeDayMask) << PrynTimeDayShift)
		| ((hour & PrynTimeHourMask) << PrynTimeHourShift)
		| ((minute & PrynTimeMinuteMask) << PrynTimeMinuteShift)
		| ((second & PrynTimeSecondMask) << PrynTimeSecondShift);
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynTimeEqual, (const PrynTime *a, const PrynTime *b))
{
	if (!a || !b) return PrynResultNullArgument;
	return (a->year == b->year && a->remainder == b->remainder) ? PrynResultTrue : PrynResultFalse;
}

PrynExport (PrynResult, prynRectangleNull, (PrynRectangle *rectangle))
{
	if (!rectangle) return PrynResultNullArgument;
	rectangle->left= rectangle->right = rectangle->top = rectangle->bottom = 0;
	return PrynResultNullArgument;
}

PrynExport (PrynResult, prynRectangle, (PrynRectangle *rectangle, int left, int top, int right, int bottom))
{
	if (!rectangle) return PrynResultNullArgument;
	rectangle->left = left;
	rectangle->top = top;
	rectangle->right = right;
	rectangle->bottom = bottom;
	return 0;
}

PrynExportVariadic (PrynResult, prynRectangleContaining, (PrynRectangle *rectangle, size_t pointCount, ...))
{
	va_list arguments;
	int *points;
	size_t index;

	if (!rectangle) return PrynResultNullArgument;
	
	va_start (arguments, pointCount);
	points = (int *) arguments;
	
	if (pointCount < 1) return prynRectangleNull (rectangle);
	prynRectangle (rectangle, points [0], points [1], points [0] + 1, points [1] + 1);
	for (index = 1; index < pointCount; index ++)
	{
		int x = points [index * 2], y = points [index * 2 + 1];
		
		if (x < rectangle->left) rectangle->left = x;
		else if (x >= rectangle->right) rectangle->right = x + 1;
		
		if (y < rectangle->top) rectangle->top = y;
		else if (y >= rectangle->bottom) rectangle->bottom = y + 1;
	}
	
	va_end (arguments);
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynRectangleExpand, (PrynRectangle *rectangle, const PrynRectangle *source, int amountX, int amountY))
{
	if (!rectangle || !source) return PrynResultNullArgument;
	return prynRectangle (rectangle, source->left - amountX, source->top - amountY, source->right + amountX, source->bottom + amountY);
}

PrynExport (PrynResult, prynSwapMemory, (void *pa, void *pb, size_t length))
{
	char *a, *b, *e;

	if (!pa || !pb) return PrynResultNullArgument;
	for (a = (char *) pa, b = (char *) pb, e = a + length; a < e; a ++, b ++)
		prynSwap (char, *a, *b);
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynSort, (void *pointer, size_t each, size_t length, PrynOrderFunction order, void *data))
{
	#define swap(A, B) prynSwapMemory ((A), (B), each)
	#define compare(A, B) order ((A), (B), data)
	const int maximumSpan = 7; /// Arrays of this or fewer elements should use insertion sort.
	char *base = (char *) pointer;
	char *stack [80];
	char **stackPointer = stack;
	char *left, *right;
	char *limit = base + length * each;
	ssize_t threshold = maximumSpan * each;
	// i -> start, j -> end, thresh -> threshold, sp -> stackPointer, _maxspan -> maximumSpan, a.length -> length, width -> each

	if (!pointer || !order) return PrynResultNullArgument;
	if (!each) return PrynResultBadArgument;
	
	while (1)
	{
		while (limit - base > threshold)
		{
			size_t centerByte = (size_t) (limit - base) >> 1;
			size_t center = centerByte - centerByte % each;
			
			swap (center + base, base); // Swap the middle element and the base.
			left = base + each; // Scans from left to right.
			right = limit - each; // Scans from right to left.
			
			// Perform a three-element sort so that *left <= *base <= *right.
			if (compare (left, right) > 0) swap (left, right);
			if (compare (base, right) > 0) swap (base, right);
			if (compare (left, base) > 0) swap (left, base);
			
			while (1)
			{
				// Move left to the right until it's above the pivot.
				do left += each; while (compare (left, base) < 0);
				
				// Move right to the left until it's below the pivot.
				do right -= each; while (compare (right, base) > 0);
				
				if (left > right) break;
				swap (left, right);
			}
			
			swap (base, right);
			
			// Push the smaller subarray.
			if (right - base > limit - left)
			{
				stackPointer [0] = base;
				stackPointer [1] = right;
				base = left;
			}
			else
			{
				stackPointer [0] = left;
				stackPointer [1] = limit;
				limit = right;
			}
			
			// Increment stack and make sure we don't overflow, which should be impossible.
			stackPointer += 2;
			if (stackPointer >= stack + sizeof (stack) / sizeof (*stack))
				return PrynResultSuccess;
		}
		
		// Insertion sort on subarray.
		left = base + each;
		while (left < limit)
		{
			right = left;
			while (right > base && compare (right - each, right) > 0)
				swap (right - each, right), right -= each;
			left += each;
		}
		
		// Pop the stack or finish up.
		if (stackPointer > stack)
		{
			stackPointer -= 2;
			base = stackPointer [0];
			limit = stackPointer [1];
		}
		else
			break;
	}

	return PrynResultSuccess;
}

static int orderInteger (const int *a, const int *b, void *data) { return *a > *b ? 1 : -1; }
static int orderReverse (const void *a, const void *b, PrynOrderFunction data) { return data (b, a, 0); }

PrynExport (PrynOrderFunction, prynOrderInteger, ()) { return (PrynOrderFunction) &orderInteger; }
PrynExport (PrynOrderFunction, prynOrderReverse, ()) { return (PrynOrderFunction) &orderReverse; }
